<template>
  <div class="puzzle-container" v-show="isVerificationShow">
    <div class="puzzle-header">
      <span class="puzzle-header-left">拖动下方滑块完成拼图</span>
      <div>
        <span class="re-btn iconfont icon-shuaxin" @click="refreshImg"></span>
        <span
            class="close-btn iconfont icon-guanbi"
            @click="closeVerificationBox"
        ></span>
      </div>
    </div>
    <div
        :style="'position:relative;overflow:hidden;width:' + dataWidth + 'px;'"
    >
      <div
          :style="
            'position:relative;width:' +
            dataWidth +
            'px;height:' +
            dataHeight +
            'px;'
          "
      >
        <img
            id="scream"
            ref="scream"
            :src="imgRandom"
            :style="'width:' + dataWidth + 'px;height:' + dataHeight + 'px;'"
        />
        <canvas
            id="puzzle-box"
            ref="puzzleBox"
            :width="dataWidth"
            :height="dataHeight"
        ></canvas>
      </div>
      <div
          class="puzzle-lost-box"
          :style="
            'left:' +
            left_Num +
            'px;width:' +
            dataWidth +
            'px;height:' +
            dataHeight +
            'px;'
          "
      >
        <canvas
            id="puzzle-shadow"
            ref="puzzleShadow"
            :width="dataWidth"
            :height="dataHeight"
        ></canvas>
        <canvas
            id="puzzle-lost"
            ref="puzzleLost"
            :width="dataWidth"
            :height="dataHeight"
        ></canvas>
      </div>
      <p
          :class="'ver-tips' + (displayTips ? ' slider-tips' : '')"
          ref="verTips"
      >
        <template v-if="verification">
          <i style="background-position: -4px -1207px"></i>
          <span style="color: #42ca6b">验证通过</span>
          <span></span>
        </template>
        <template v-if="!verification">
          <i style="background-position: -4px -1229px"></i>
          <span style="color: red">验证失败:</span>
          <span style="margin-left: 4px">拖动滑块将悬浮图像正确拼合</span>
        </template>
      </p>
    </div>
    <div class="slider-container" :style="'width:' + dataWidth + 'px;'">
      <div class="slider-bar"></div>
      <div
          class="slider-btn"
          ref="sliderBtn"
          @mousedown="startMove"
          @touchstart="startMove"
      ></div>
    </div>
  </div>
</template>
<script setup lang="ts">
import {
  ref,
  defineEmits,
  defineProps,
  onBeforeMount,
  onMounted,
  onBeforeUnmount,
  watch,
  nextTick,
} from "vue";

const emits = defineEmits(["update:modelValue"]);
const props = defineProps({
  // 画布图片的尺寸
  width: {
    type: [String, Number],
    default: 260,
  },
  height: {
    type: [String, Number],
    default: 120,
  },
  // 图集
  puzzleImgList: {
    type: Array,
    default: () => [
      new URL('@/verification/assets/thumbnail-img01.jpg', import.meta.url),
      new URL('@/verification/assets/thumbnail-img02.jpg', import.meta.url),
      new URL('@/verification/assets/thumbnail-img03.jpg', import.meta.url)
    ],
  },
  // 滑块的大小
  blockSize: {
    type: [String, Number],
    default: 40,
  },
  // 误差
  deviation: {
    type: [String, Number],
    default: 4,
  },
  // 滑块圆角的大小（仅当其形状是square有效）
  blockRadius: {
    type: [String, Number],
    default: 4,
  },
  // 滑块随机出现的范围
  wraperPadding: {
    type: [String, Number],
    default: 20,
  },
  // 滑块形状 square  puzzle
  blockType: {
    type: String,
    default: "square",
  },
  // 成功的回调
  onSuccess: {
    type: Function,
    default: () => {
      console.log("成功");
    },
  },
  // 失败的回调
  onError: {
    type: Function,
    default: () => {
      console.log("失败");
    },
  },
  verificationShow: {
    type: Boolean,
    default: false,
  },
  modelValue: {
    type: Boolean,
    default: false
  },
});
const isVerificationShow = ref(false);
const moveStart = ref<any>("");
const displayTips = ref(false);
const verification = ref(false);
const randomX = ref(0);
const randomY = ref(0);
const imgRandom = ref<any>("");
const left_Num = ref(0);
const dataWidth = ref(0);
const dataHeight = ref(0);
const puzzleSize = ref(0); // 滑块的大小
const deviationDate = ref(0);
const radiusData = ref(0);
const paddingDate = ref(0);
const puzzleBox = ref<any>(null); //元素标识
const puzzleLost = ref<any>(null); //元素标识
const puzzleShadow = ref<any>(null); //元素标识
const sliderBtn = ref<any>(null); //元素标识
/* 关闭验证 */
const closeVerificationBox = () => {
  isVerificationShow.value = false;
};
/* 刷新 */
const refreshImg = () => {
  let imgRandomIndex = Math.round(
      Math.random() * (props.puzzleImgList.length - 1)
  );
  imgRandom.value = props.puzzleImgList[imgRandomIndex];
  initCanvas();
};
/* 画布初始化 */
const initCanvas = () => {
  clearCanvas();
  let w = dataWidth.value;
  let h = dataHeight.value;
  let PL_Size = puzzleSize.value;
  let padding = paddingDate.value;
  let MinN_X = padding + PL_Size;
  let MaxN_X = w - padding - PL_Size - PL_Size / 6;
  let MaxN_Y = padding;
  let MinN_Y = h - padding - PL_Size - PL_Size / 6;
  randomX.value = Math.round(Math.random() * (MaxN_X - PL_Size) + MinN_X);
  randomY.value = Math.round(Math.random() * MaxN_Y + MinN_Y);
  let X = randomX.value;
  let Y = randomY.value;
  left_Num.value = -X + 10;
  let d = PL_Size / 3;
  let radius = Number(radiusData.value);
  let c = puzzleBox.value;
  let c_l = puzzleLost.value;
  let c_s = puzzleShadow.value;
  let ctx = c.getContext("2d");
  let ctx_l = c_l.getContext("2d");
  let ctx_s = c_s.getContext("2d");
  ctx.globalCompositeOperation = "xor";
  ctx.shadowBlur = 10;
  ctx.shadowColor = "#fff";
  ctx.shadowOffsetX = 3;
  ctx.shadowOffsetY = 3;
  ctx.fillStyle = "rgba(0,0,0,0.7)";
  ctx.beginPath();
  ctx.lineWidth = "1";
  ctx.strokeStyle = "rgba(0,0,0,0)";
  if (props.blockType === "square") {
    ctx.arc(X + radius, Y + radius, radius, Math.PI, (Math.PI * 3) / 2);
    ctx.lineTo(PL_Size - radius + X, Y);
    ctx.arc(
        PL_Size - radius + X,
        radius + Y,
        radius,
        (Math.PI * 3) / 2,
        Math.PI * 2
    );
    ctx.lineTo(PL_Size + X, PL_Size + Y - radius);
    ctx.arc(
        PL_Size - radius + X,
        PL_Size - radius + Y,
        radius,
        0,
        (Math.PI * 1) / 2
    );
    ctx.lineTo(radius + X, PL_Size + Y);
    ctx.arc(
        radius + X,
        PL_Size - radius + Y,
        radius,
        (Math.PI * 1) / 2,
        Math.PI
    );
  } else {
    ctx.moveTo(X, Y);
    ctx.lineTo(X + d, Y);
    ctx.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y);
    ctx.lineTo(X + 3 * d, Y);
    ctx.lineTo(X + 3 * d, Y + d);
    ctx.bezierCurveTo(
        X + 2 * d,
        Y + d,
        X + 2 * d,
        Y + 2 * d,
        X + 3 * d,
        Y + 2 * d
    );
    ctx.lineTo(X + 3 * d, Y + 3 * d);
    ctx.lineTo(X, Y + 3 * d);
  }
  ctx.closePath();
  ctx.stroke();
  ctx.fill();
  let img = new Image();
  img.src = imgRandom.value;
  img.onload = function () {
    ctx_l.drawImage(img, 0, 0, w, h);
  };
  ctx_l.beginPath();
  ctx_l.strokeStyle = "rgba(0,0,0,0)";
  if (props.blockType === "square") {
    ctx_l.arc(X + radius, Y + radius, radius, Math.PI, (Math.PI * 3) / 2);
    ctx_l.lineTo(PL_Size - radius + X, Y);
    ctx_l.arc(
        PL_Size - radius + X,
        radius + Y,
        radius,
        (Math.PI * 3) / 2,
        Math.PI * 2
    );
    ctx_l.lineTo(PL_Size + X, PL_Size + Y - radius);
    ctx_l.arc(
        PL_Size - radius + X,
        PL_Size - radius + Y,
        radius,
        0,
        (Math.PI * 1) / 2
    );
    ctx_l.lineTo(radius + X, PL_Size + Y);
    ctx_l.arc(
        radius + X,
        PL_Size - radius + Y,
        radius,
        (Math.PI * 1) / 2,
        Math.PI
    );
  } else {
    ctx_l.moveTo(X, Y);
    ctx_l.lineTo(X + d, Y);
    ctx_l.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y);
    ctx_l.lineTo(X + 3 * d, Y);
    ctx_l.lineTo(X + 3 * d, Y + d);
    ctx_l.bezierCurveTo(
        X + 2 * d,
        Y + d,
        X + 2 * d,
        Y + 2 * d,
        X + 3 * d,
        Y + 2 * d
    );
    ctx_l.lineTo(X + 3 * d, Y + 3 * d);
    ctx_l.lineTo(X, Y + 3 * d);
  }
  ctx_l.closePath();
  ctx_l.stroke();
  ctx_l.shadowBlur = 10;
  ctx_l.shadowColor = "black";
  ctx_l.clip();
  ctx_s.beginPath();
  ctx_s.lineWidth = "1";
  ctx_s.strokeStyle = "rgba(0,0,0,0)";
  if (props.blockType === "square") {
    ctx_s.arc(X + radius, Y + radius, radius, Math.PI, (Math.PI * 3) / 2);
    ctx_s.lineTo(PL_Size - radius + X, Y);
    ctx_s.arc(
        PL_Size - radius + X,
        radius + Y,
        radius,
        (Math.PI * 3) / 2,
        Math.PI * 2
    );
    ctx_s.lineTo(PL_Size + X, PL_Size + Y - radius);
    ctx_s.arc(
        PL_Size - radius + X,
        PL_Size - radius + Y,
        radius,
        0,
        (Math.PI * 1) / 2
    );
    ctx_s.lineTo(radius + X, PL_Size + Y);
    ctx_s.arc(
        radius + X,
        PL_Size - radius + Y,
        radius,
        (Math.PI * 1) / 2,
        Math.PI
    );
  } else {
    ctx_s.moveTo(X, Y);
    ctx_s.lineTo(X + d, Y);
    ctx_s.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y);
    ctx_s.lineTo(X + 3 * d, Y);
    ctx_s.lineTo(X + 3 * d, Y + d);
    ctx_s.bezierCurveTo(
        X + 2 * d,
        Y + d,
        X + 2 * d,
        Y + 2 * d,
        X + 3 * d,
        Y + 2 * d
    );
    ctx_s.lineTo(X + 3 * d, Y + 3 * d);
    ctx_s.lineTo(X, Y + 3 * d);
  }
  ctx_s.closePath();
  ctx_s.stroke();
  ctx_s.shadowBlur = 20;
  ctx_s.shadowColor = "black";
  ctx_s.fill();
};
/* 通过重置画布尺寸清空画布，这种方式更彻底 */
const clearCanvas = () => {
  let c = puzzleBox.value;
  let c_l = puzzleLost.value;
  let c_s = puzzleShadow.value;
  c.setAttribute("height", c.getAttribute("height"));
  c_l.setAttribute("height", c.getAttribute("height"));
  c_s.setAttribute("height", c.getAttribute("height"));
};
/* 按住滑块后初始化移动监听，记录初始位置 */
const startMove = (e: any) => {
  e = e || window.event;
  sliderBtn.value.style.backgroundPosition = "0 -216px";
  moveStart.value = e.pageX || e.targetTouches[0].pageX;
  addMouseMoveListener();
};
/* 滑块移动 */
const moving = (e: any) => {
  e = e || window.event;
  let moveX = e.pageX || e.targetTouches[0].pageX;
  let d = moveX - moveStart.value;
  let w = dataWidth.value;
  let PL_Size = puzzleSize.value;
  let padding = paddingDate.value;
  if (moveStart.value === "") {
    return "";
  }
  if (d < 0 || d > w - padding - PL_Size) {
    return "";
  }
  sliderBtn.value.style.left = d + "px";
  sliderBtn.value.style.transition = "inherit";
  puzzleLost.value.style.left = d + "px";
  puzzleLost.value.style.transition = "inherit";
  puzzleShadow.value.style.left = d + "px";
  puzzleShadow.value.style.transition = "inherit";
};
/* 移动结束，验证并回调 */
const moveEnd = (e: any) => {
  e = e || window.event;
  let moveEnd_X = (e.pageX || e.changedTouches[0].pageX) - moveStart.value;
  let ver_Num = randomX.value - 10;
  let deviationValue = deviationDate.value;
  let Min_left = ver_Num - deviationValue;
  let Max_left = ver_Num + deviationValue;
  if (moveStart.value !== "") {
    if (Max_left > moveEnd_X && moveEnd_X > Min_left) {
      displayTips.value = true;
      verification.value = true;
      setTimeout(function () {
        displayTips.value = false;
        initCanvas();
        /* 成功的回调函数 */
        props.onSuccess();
      }, 500);
    } else {
      displayTips.value = true;
      verification.value = false;
      setTimeout(function () {
        displayTips.value = false;
        initCanvas();
        /* 失败的回调函数 */
        props.onError();
      }, 800);
    }
  }
  if (
      typeof sliderBtn.value !== "undefined" &&
      typeof puzzleLost.value !== "undefined" &&
      typeof puzzleShadow.value !== "undefined"
  ) {
    setTimeout(function () {
      sliderBtn.value.style.left = 0;
      sliderBtn.value.style.transition = "left 0.5s";
      puzzleLost.value.style.left = 0;
      puzzleLost.value.style.transition = "left 0.5s";
      puzzleShadow.value.style.left = 0;
      puzzleShadow.value.style.transition = "left 0.5s";
    }, 400);
    sliderBtn.value.style.backgroundPosition = "0 -84px";
  }
  moveStart.value = "";
};
/* 全局绑定滑块移动与滑动结束，移动过程中鼠标可在页面任何位置 */
const addMouseMoveListener = () => {
  document.addEventListener("mousemove", moving);
  document.addEventListener("touchmove", moving);
  document.addEventListener("mouseup", moveEnd);
  document.addEventListener("touchend", moveEnd);
};
watch(
    //监控数据变化
    () => isVerificationShow.value,
    (newVal, _d) => {
      emits("update:modelValue", newVal);
    },
    {immediate: true}
);
watch(
    () => props.modelValue,
    (val) => {
      isVerificationShow.value = val;
    }
)
watch(
    //监控数据变化
    () => props.verificationShow,
    (newVal, _d) => {
      isVerificationShow.value = newVal;
    }
);
onBeforeMount(() => {
  // 随机显示一张图片
  let imgRandomIndex = Math.round(
      Math.random() * (props.puzzleImgList.length - 1)
  );
  imgRandom.value = props.puzzleImgList[imgRandomIndex];
  puzzleSize.value = Number(props.blockSize);
  deviationDate.value = Number(props.deviation);
  radiusData.value = Number(props.blockRadius);
  dataWidth.value = Number(props.width);
  dataHeight.value = Number(props.height);
  paddingDate.value = Number(props.wraperPadding) || 20;
});
onMounted(() => {
  nextTick(() => {
    initCanvas();
  });
});
onBeforeUnmount(() => {
  document.removeEventListener("mousemove", moving);
  document.removeEventListener("touchmove", moving);
  document.removeEventListener("mouseup", moveEnd);
  document.removeEventListener("touchend", moveEnd)
});
</script>
<style scoped>
.slider-btn {
  position: absolute;
  width: 44px;
  height: 44px;
  left: 0;
  top: -7px;
  z-index: 12;
  cursor: pointer;
  background-image: url(../assets/sprite.3.2.0.png);
  background-position: 0 -84px;
  transition: inherit;
}

.ver-tips {
  position: absolute;
  left: 0;
  bottom: -22px;
  background: rgba(255, 255, 255, 0.9);
  height: 22px;
  line-height: 22px;
  font-size: 12px;
  width: 100%;
  margin: 0;
  text-align: left;
  padding: 0 8px;
  transition: all 0.4s;
}

.slider-tips {
  bottom: 0;
}

.ver-tips i {
  display: inline-block;
  width: 22px;
  height: 22px;
  vertical-align: top;
  background-image: url(../assets/sprite.3.2.0.png);
  background-position: -4px -1229px;
}

.ver-tips span {
  display: inline-block;
  vertical-align: top;
  line-height: 22px;
  color: #455;
}

.active-tips {
  display: block;
}

.hidden {
  display: none;
}

.puzzle-container {
  position: relative;
  display: inline-block;
  padding: 15px 15px 28px;
  border: 1px solid #ddd;
  background: #ffffff;
  border-radius: 16px;
}

.puzzle-header {
  display: flex;
  justify-content: space-between;
  margin: 5px 0;
}

.puzzle-header-left {
  color: #333;
}

.re-btn,
.close-btn {
  font-size: 16px;
  cursor: pointer;
  color: #666;
}

.re-btn:hover {
  color: #67c23a;
}

.close-btn:hover {
  color: #f56c6c;
}

.close-btn {
  margin-left: 5px;
}

.slider-container {
  position: relative;
  margin: 10px auto 0;
  min-height: 15px;
}

.slider-bar {
  height: 10px;
  border: 1px solid #c3c3c3;
  border-radius: 5px;
  background: #e4e4e4;
  box-shadow: 0 1px 1px rgba(12, 10, 10, 0.2) inset;
  position: absolute;
  width: 100%;
  top: 7px;
}

#puzzle-box {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 22;
}

#puzzle-shadow {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 22;
}

#puzzle-lost {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 33;
}

.puzzle-lost-box {
  position: absolute;
  width: 260px;
  height: 116px;
  left: 0;
  top: 0;
  z-index: 111;
}

@font-face {
  font-family: "iconfont";
  src: url("../assets/icon-font/iconfont.eot?t=1565160368550"); /* IE9 */
  src: url("../assets/icon-font/iconfont.eot?t=1565160368550#iefix") format("embedded-opentype"),
    /* IE6-IE8 */ url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAANUAAsAAAAAByQAAAMIAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCfAqCFIIkATYCJAMMCwgABCAFhG0HOhtkBsi+QDw2JxWVhBKVFukepjkT9iz2vr6/fAq8RxAPz4+9O/e9O9WkGj15Fk3LQ/NCJ41QaVASVSzTSJ7E2p//a/4CvASvALwTjbaVddq9o71hlfDegDawAW//1zQFGqAwEaokh6yK8jD/9/c4lxuw3PPkdI3ZegseIJGz/1WKn5AESUiaSApHHzJ2/2uCAT/S0Iz7q0i8eCKrBLgEQJauMPX/uZzeKPH5KSuXuSYd9aI4DiigvbFNViAJeovsrkAs8jyBdmME8ODibgRNyZwWiIdGO9DM+KXkU61Cc8XaFI9p0loe6Q3gNvh+fKdFk6KpMuceX5/7cPTJ/Tfy4spDEIxnBb+JihVAEueV9kOVKL4Ca1dZq6YAKXljibz/PCFRiGZmcSdYIInCJw6Toid+iM6+oYJmd5A8BW4pefDQFKWzc2SkXpr+P1LN9+L/JeF4UsZPUGWa/nUPMypPfme7Do2j785+zvL4Z37s1l/wcAEwhNPJqPMcHgerCdZ/wbrLlYRrg2tSMZ45fpHJLQ7humLaL9WW9r64yBa+7D3UcBOs/3jDHKz7+MTzgPkExVfzZnHa3qveHHJTfVjsFjh5X99QxlP8cdhYHOyY+Fmy4BUXoLG2ngiSbe2xv/AbW/8ptNb3/tiQ4MN+w2saBeoEoFUBLHjnAmBTaiLTUFPhO3zDUa2nvAqYK7g0MN39vv3VQzeXDK2GEihajELVagZN8go06bAKzVptQ7tlzuYOAyoqItuwZIgg9LpC0e05VL1eaJI/0GTYD5r1RgXaXcb2nh3mwiydMjlCPrpPaLwkVrZJlsLSAekidDgtCigT4tyEsCcp+dQlxcRjLMjvdV9EoeIkwgt0GYVhgiknc/KkPRNJdzpyXPWmtpdEsGQfIw5BfMj3BGU8iZjyOoulwucHiFYIObiBUGWfICxnekcqiQJAL+UxiHAt11Td0/pqhIJiLBFBNrKS0IonUKl61BzxiLa0RzS1QyatYqi8Pb8yer5d0M7co0aJGqn5TuHErmnksyD2aGIA") format("woff2"),
  url("../assets/icon-font/iconfont.woff?t=1565160368550") format("woff"),
  url("../assets/icon-font/iconfont.ttf?t=1565160368550") format("truetype"),
    /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url("../assets/icon-font/iconfont.svg?t=1565160368550#iconfont") format("svg"); /* iOS 4.1- */
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-guanbi:before {
  content: "\f01f1";
}

.icon-shuaxin:before {
  content: "\e609";
}
</style>